import { useEffect, useDebugValue, useRef } from 'react';
import { callAPI } from './callApi';
import { ICallService } from './callService';
import { useAsyncState } from './useAsyncState';
import { IAsyncState } from './IAsyncState';

type callFn = typeof callAPI | ICallService;

// function example() {
//   const as = useCall([orgCode, refreshCount], () => [
//     "get /oss/v1.0/nc/ncInfos", {
//       orgCode,
//       param1,
//       param2,
//     }]);
// }

interface IAsyncOptions {
  ForceReExecuteDelay?: number,
  LoadingDelay?: number,
}

const defaultOption: IAsyncOptions = {
  ForceReExecuteDelay: 5000, // 等待多长时间才允许手动强制刷新API调用
  LoadingDelay: 0,
}

/**
 * 高阶函数，根据具体的 (req)=>Promise(resp)> API，和响应标准化函数，生成 useApiXXX hook。
 * 可以对各种 API 轻松的包装成 useApiXXX hook，让这些 API 更容易集成到 react 应用开发环境
 * @param callApi (req)=>Promise(resp) API。
 * @param responseStandardize 响应标准化函数
 */
export function createApiHook(callApi:callFn = callAPI, responseStandardize: Function) {
  /**
   * 用于集成现有的 services 函数
   * const {p,o,e,r,d} = useApi(()=>[servicePath, request], deps, condition)
   * servicePath:
   *   get /oss/xxx
   *   post /oss/xxx
   * e 是 Error 类型
   */
  function useApi(deps: any[], getFactors: any, options?: IAsyncOptions) {
    const config = {...defaultOption, ...options};
    const pathRef = useRef();
    useDebugValue(pathRef.current);

    const as = useAsyncState();
    const condition = deps.length ? (!!deps[0]) : true;
    let didCancel = false;
    let didFinish = false;

    function execute() {
      if (!condition) return;
      const [servicePath, request] = getFactors();
      if (servicePath instanceof Function) {
        pathRef.current = servicePath.name;
      } else {
        pathRef.current = servicePath;
      }
      const debugName = pathRef.current;

      const bTime = Date.now();
      function forceReExecute() {
        if ((Date.now() - bTime) < config.ForceReExecuteDelay) {
          return; // 如果距离上次 execute 时间太近，则不允许强制执行
        }
        execute();
      }
      function cancel() {
        didCancel = true;
      }

      let markedPending = false;
      function markPending() {
        markedPending = true;
        as.ss(ts => ({
          ...ts,
          s: ts.s + 1,
          o: false,
          e: false,
          p: true,
          b: new Date(),
          f: undefined,
          u: debugName,
          r: request,
          d: undefined,
          E: undefined,
          rsp: null,
          re: forceReExecute, // 允许手动重新执行，当遇到服务暂态不可用或者服务端修改了重试
          cancel, // 允许手动提前设置取消响应处理
        } as IAsyncState));
      }

      if (config.LoadingDelay) {
        setTimeout(() => {
          if (didFinish) return; // 已经执行完了，不用再设置 pending 了。
          markPending();
        }, config.LoadingDelay);
      } else {
        markPending();
      }

      const promise = callApi(servicePath, request);
      promise.then((resp: any) => {
        didFinish = true;
        if (didCancel) {
          console.warn(
            `service ${debugName} is cancelled due to deps change ${JSON.stringify(
              deps,
            )}`,
          );
          return;
        }
        if (!markedPending) {
          markPending()
        }
        const std = responseStandardize(resp);
        let asNext;
        as.ss(ts => {
          asNext = {
            ...ts,
            rsp: resp,
            p: false,
            f: new Date(),
            ...std,
          };
          return asNext;
        });
        return asNext;

      });
      promise.catch((error: any) => {
        didFinish = true;
        if (didCancel) {
          console.warn(
            `service ${debugName} is cancelled due to deps change ${JSON.stringify(
              deps,
            )}`,
          );
          return;
        }
        if (!markedPending) {
          markPending()
        }
        as.ss(ts => ({
          ...ts,
          p: false,
          f: new Date(),
          o: false,
          e: error,
          E: error,
        }));
      });
    }

    useEffect(() => {
      if (!condition) return;
      execute();
      return () => {
        didCancel = true;
      };
    }, deps);
    return as;
  }
  return useApi;
}
